import time
import cv2
import tkinter as tk
from tkinter import font
from PIL import Image,ImageTk

import socket
import select

from usertable import *
from usertable import globalVal
from stringhandle import *
from certificate import *

from gmssl import sm4

def startlisten(localIP,localport):#本地套接字监听
	skt_chat=(localIP,localport)
	skchat=socket.socket()
	skchat.bind(skt_chat)
	skchat.listen(5)
	return skchat

def makereceiveconnection(localIP,localport):#建立接收连接，实质内容
	r=startlisten(localIP,localport)
	s=0
	sessionkey=''
	r.settimeout(300)#超时时间5分钟，时间一到就说明现在没人要和自己联系
	while s==0:
		try:
			result= r.accept()#接受建立连接的请求
		except socket.timeout:#超时就报错
			puterror_win("连接已超时")
			r.close()
			return None
		sk=result[0]#获取来自发送方的发送端口之套接字
		
		sk.send(bytes(str(result[1][1]).encode()))#由于发送方的发送端口不确定，这里进行反馈
		x=makerecveiveconnection_certverify(sk)
		if x==False:
			puterror_win("证书验签失败，通信已被阻止")
			sk.close()
			return None
		keychangemessage=sk.recv(14)#收到来自发送方的疑问：询问接收方密钥因子和总密钥的和
		if keychangemessage==b"ask_key_factor":#确认发送方的疑问
			sk.send(b"ask_key_factor")#向发送方询问 发送方密钥因子和总密钥的和
			keylength=len(globalVal.mainkey)#获取密钥长度
			keyfactor_receive=randomstring(keylength)#接收方密钥因子生成
			key_receivesubmain=stringadd_samelength(keyfactor_receive,globalVal.mainkey)#接收方密钥因子加上总密钥
			sk.send(key_receivesubmain.encode('utf-8'))#差值反馈给发送方
			keychangemessage=sk.recv(keylength).decode('utf-8')#接收和值
			sessionkey=stringadd_samelength(keychangemessage,key_receivesubmain)#生成会话密钥
		s=1#退出类死循环
	
	if(useronline_tablecheck(result[0],result[1][1],1)==None):#如果发现该用户在表中不存在则加入
		u=onlineuser(result[1][0])#建立连线用户对象加到连线用户表中
		u.receivesocket=r#将用于接收来自该用户数据的套接字 送入连线用户对象中
		u.receiveen=True#宣布本机可以从对方接受数据
		u.receiveport_l=localport#设置用于接收的端口号
		u.sendport_r=result[1][1]#搞清楚对方用哪一个端口号发送
		u.sessionkey=sessionkey#记下会话密钥
		globalVal.useronline_table.append(u)#在连线用户表中记载
		globalVal.receivesocket.append(sk)#记下用于接收数据的套接字
	else:#如果已经有了就算了
		pass
	return result

def makereceiveconnect_win():#建立接收连接，图形界面
	
	def makeconnect_button():#按钮触发实质内容
		localIP=IP_in.get()
		localport=int(port_in.get())
		r=makereceiveconnection(localIP,localport)
		if r==None:
			return
		elif r[0]!='' and r[0]!=None:#搞清楚对方是谁
			prompt3.config(text="对方IP"+str(r[1][0])+" 端口号"+str(r[1][1]))
		else:
			prompt3.config(text="连线失败")
	
	receiveconwin=tk.Toplevel()
	receiveconwin.geometry("300x130")
	receiveconwin.title("建立接收连接")
	prompt1=tk.Label(receiveconwin,text="请输入自身的IP地址（或主机名）和端口号")
	promptIP=tk.Label(receiveconwin,text="本地IP   ")
	promptport=tk.Label(receiveconwin,text="接收端口号")
	prompt2=tk.Label(receiveconwin,text="默认接收端口号为6544，发送端口号随机")
	prompt3=tk.Label(receiveconwin,text="连线工作中……")
	IP_in=tk.Entry(receiveconwin,width=20,)
	port_in=tk.Entry(receiveconwin,width=20)
	
	IP_in.insert(0,socket.gethostname())
	if port_in.get()=='':
		port_in.insert(0,"6544")#因为默认接收在6544端口，连线用户表也有自身，所以就自动填写
	promptIP.grid(row=1,column=0)
	promptport.grid(row=2,column=0)
	IP_in.grid(row=1,column=1)
	port_in.grid(row=2,column=1)
	prompt1.grid(row=0,column=0,columnspan=3)
	prompt2.grid(row=3,column=0,columnspan=3)
	prompt3.grid(row=4,column=0,columnspan=3)
	conbutton=tk.Button(receiveconwin,text="建立连接",command=lambda:makeconnect_button())
	conbutton.grid(row=2,column=2)
	receiveconwin.mainloop()

def receivetext(contentText,thereceivesocket,messagebytes):#文本消息接收功能
	sessionkey=""#建立会话密钥变量
	sk=thereceivesocket.getpeername()
	for x in globalVal.useronline_table:
		if x.userIP==sk[0] and x.sendport_r==sk[1]:
			sessionkey=x.sessionkey#获取会话密钥的值
	decrypter=sm4.CryptSM4()#启用临时解密机
	decrypter.set_key(sessionkey.encode('utf-8'),sm4.SM4_DECRYPT)#设置解密密钥
	message=decrypter.crypt_ecb(messagebytes).decode('utf-8')#解密得知原始消息

	sk=thereceivesocket.getpeername()
	contentText.insert(tk.END,"来自"+sk[0]+":"+str(sk[1])+"的消息 "+time.asctime()+'：','name')
	contentText.insert(tk.END,message,'text')#输出明文
	contentText.insert(tk.END,"\n")

def receivefile(contentText,thereceivesocket):#接收文件功能
	sk=thereceivesocket.getpeername()#接收方套接字
	messagebytes=thereceivesocket.recv(256)#获取在UTF-8编码后的文件名
	filename=getfilename_from_string256(messagebytes.decode('utf-8'))#文件名解码
	fnew=open(filename,'wb')#新建文件，即为所接收之物，出于一般性考虑采取二进制方式
	
	contentText.insert(tk.END,"来自"+sk[0]+":"+str(sk[1])+"的文件："+filename+" 开始传送\t",'file')
	contentText.insert(tk.END,time.asctime()+'：\n','file')
	contentText.update()
	while True:
		try:

			messagebytes=thereceivesocket.recv(11)#获取报头
			if(messagebytes==b"[file    ]m"):#如果发现是文件的中间部分，则继续接收
				messagebytes=thereceivesocket.recv(4)#获取数据部分长度
				datalength=int((str)(messagebytes.decode('utf-8')))#对数据部分长度的值进行解码
				messagebytes=thereceivesocket.recv(datalength)#按照这个长度设置缓冲区大小接收内容
				fnew.write(messagebytes)#写入文件内容
				thereceivesocket.send(b"receive successfully")#及时反馈给发送方说当前数据成功收到
			elif(messagebytes==b"[file    ]d"):#如果发现是文件结束的声明则停止接收
				break
			else:
				puterror_win("文件传送过程出错")#报头不对就报错
				thereceivesocket.send(b"receive failed      ")
				break

		except socket.timeout:#迟迟收不到消息就报错
			useronline_tabledelete(sk[0],sk[1],0)
			puterror_win("与"+sk[0]+":"+(str)(sk[1])+"的连接超时，文件传送失败")
			del globalVal.receivesocket[globalVal.receivesocket.index(x)]
			break
		except ConnectionResetError:#连接已重置也要报错
			useronline_tabledelete(sk[0],sk[1],0)
			puterror_win("与"+sk[0]+":"+(str)(sk[1])+"的连接已重置，文件传送失败")
			del globalVal.receivesocket[globalVal.receivesocket.index(x)]
			break
	fnew.close()#关闭文件
	
	contentText.insert(tk.END,"传送完毕\t",'file')#输出提示信息
	contentText.insert(tk.END,time.asctime()+'：\n','file')

def receiveimage(contentText,thereceivesocket):#接收图片功能，核心过程和文件传输一模一样，只是不保证可靠 
	sk=thereceivesocket.getpeername()
	messagebytes=thereceivesocket.recv(256)#获取在UTF-8编码后的文件名
	filename=getfilename_from_string256(messagebytes.decode('utf-8'))#文件名解码
	fnew=open(filename,'wb')#新建文件，即为所接收之物，出于一般性考虑采取二进制方式
	while True:
		messagebytes=thereceivesocket.recv(11)#获取报头
		if(messagebytes==b"[image   ]m"):#如果发现是文件的中间部分，则继续接收
			messagebytes=thereceivesocket.recv(4)#获取数据部分长度
			datalength=int((str)(messagebytes.decode('utf-8')))#对数据部分长度的值进行解码
			messagebytes=thereceivesocket.recv(datalength)#按照这个长度设置缓冲区大小接收内容
			fnew.write(messagebytes)#写入文件内容
		elif(messagebytes==b"[image   ]d"):#如果发现是文件结束的声明则停止接收
			break
	fnew.close()#关闭文件
	contentText.insert(tk.END,"来自"+sk[0]+":"+str(sk[1])+"的图片："+filename,'file')
	photo=cv2.imread(filename)#Text控件容易出错，这里就拿openCV显示
	cv2.imshow("来自"+sk[0]+":"+str(sk[1])+"的图片："+filename,photo)
	contentText.insert(tk.END,"\n")#输出提示信息

def receivemessage(chatwin,contentText):#接收一切消息的总管理系统
	time.sleep(0.01)
	message=''
	for x in globalVal.receivesocket:
		mlist,m2,m3=select.select([x],[],[],0)#非阻塞式检察有无新的内容
		if(mlist):
			try:
				messagebytes=x.recv(11)#如果有新的内容就接收
			except ConnectionResetError:#一旦连接已重置，就报错
				sk=x.getpeername()
				globalVal.receivesocket.remove(x)#这个receivesocket被删除
				puterror_win("连接已重置\n用户"+sk[0]+":"+str(sk[1]))
				
			if(messagebytes==b"[text    ]t"):#如果发觉这是一个文本，则显示
				messagebytes=x.recv(4096)#一次最多发送4kB文字
				#因为传过来的内容是utf-8编码后的bytes类型，这里需要解码后转为string否则就无法识别其中的转义字符
				receivetext(contentText,x,messagebytes)#把传来的文本内容输出在聊天框上
			elif(messagebytes==b"[file    ]s"):#如果发觉这是一个文件，则接收
				receivefile(contentText,x)
			elif(messagebytes==b"[image   ]s"):#如果发觉这是一张图片，就输出
				receiveimage(contentText,x)